#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
#include <linux/device.h>
#include <linux/poll.h>

#define BUF_SIZE 64
#define DEVICE_NUM 2

unsigned int g_dev_num = 0;

struct c0_dev{
	struct cdev *dev;
	dev_t dev_num;
	char dev_name[BUF_SIZE];
	struct class *devcls;
//	struct device *dev;

	wait_queue_head_t rq;
	wait_queue_head_t wq;
	struct kfifo c0_fifo;
};
struct c0_data{
	struct c0_dev *dev_c0;
	char name[BUF_SIZE];
};

struct c0_dev *c0;

int c0_open(struct inode *inode,struct file *file)
{
	
	unsigned int minor = iminor(inode);
	struct c0_data *data;
	struct c0_dev *dev_c = c0+minor;
	printk("%s major : %d, minor : %d device : %s\n",__func__,
			MAJOR(inode->i_rdev),MINOR(inode->i_rdev),dev_c->dev_name);
	data = (struct c0_data *)kmalloc(sizeof(struct c0_data),GFP_KERNEL);
	if(!data)
		return -ENOMEM;
	sprintf(data->name,"data_%d",minor);
	data->dev_c0 = dev_c;
	file->private_data = data;

	return 0;
}
int c0_close(struct inode *inode,struct file *file)
{
	struct c0_data *data = file->private_data;
	printk("%s\n",__func__);
	kfree(data);
	return 0;
}

ssize_t c0_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
	unsigned int read_size;
	int ret;
	struct c0_data *data = filp->private_data;
	struct c0_dev *c0 = data->dev_c0;
	if(kfifo_is_empty(&c0->c0_fifo))
	{
		if(filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		
		printk("%s fifo empty...sleeping....\n",c0->dev_name);
		ret = wait_event_interruptible(c0->rq,!kfifo_is_empty(&c0->c0_fifo));
		if(ret)
			return ret;

	}
	ret = kfifo_to_user(&c0->c0_fifo,buf,count,&read_size);
	if(ret)
		return -EIO;
	if(!kfifo_is_full(&c0->c0_fifo))
		wake_up_interruptible(&c0->wq);
	*ppos += read_size;
	printk("read_size = %d, pos = %lld\n",read_size,*ppos);
	return read_size;
}
ssize_t c0_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{ 
	unsigned int written_size;
	int ret;
	struct c0_data *data = filp->private_data;
	struct c0_dev *c0 = data->dev_c0;
	if(kfifo_is_full(&c0->c0_fifo))
	{
		if(filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		printk("%s fifo full...sleeping....\n",c0->dev_name);
		ret = wait_event_interruptible(c0->wq,!kfifo_is_full(&c0->c0_fifo));
		if(ret)
			return ret;
	}
	ret = kfifo_from_user(&c0->c0_fifo,buf,count,&written_size);
	if(ret)
		return -EIO;
	*ppos += written_size;
	if(!kfifo_is_empty(&c0->c0_fifo))
		wake_up_interruptible(&c0->rq);
	printk("written_size = %d,pos = %lld\n",written_size,*ppos);
	return written_size;
}
unsigned int c0_poll(struct file *filp,struct poll_table_struct *wait)
{
	int mask = 0;
	
	struct c0_data *data = filp->private_data;
	struct c0_dev *c0 = data->dev_c0;

	printk("%s\n",__func__);
	poll_wait(filp, &c0->rq, wait);
	poll_wait(filp, &c0->wq, wait);

	if (!kfifo_is_empty(&c0->c0_fifo))
		mask |= POLLIN | POLLRDNORM;
	if (!kfifo_is_full(&c0->c0_fifo))
		mask |= POLLOUT | POLLWRNORM;
										
	return mask;
}

const struct file_operations c0_fops={
	.open    = c0_open,
	.read    = c0_read,
	.write   = c0_write,
	.release = c0_close,
	.poll    = c0_poll, 
};

int chrdev_setup(struct c0_dev *c0,int maj_num,int index)
{
	int ret;
	c0->dev_num = MKDEV(maj_num,index);
	c0->dev = cdev_alloc();
	if (!c0->dev) {
		printk("cdev_alloc failed\n");
		unregister_chrdev_region(c0->dev_num,1);
		return -ENOMEM;
	}
	cdev_init(c0->dev,&c0_fops);
	ret=cdev_add(c0->dev,c0->dev_num,1);
	if(ret)
	{
		printk("cdev add failed\n");
		cdev_del(c0->dev);
		return ret;
	}
	sprintf(c0->dev_name,"c0_%d",index);

	c0->devcls = class_create(THIS_MODULE,c0->dev_name);
	device_create(c0->devcls,NULL,c0->dev_num,NULL,c0->dev_name);

	init_waitqueue_head(&c0->rq);
	init_waitqueue_head(&c0->wq);
	
	ret = kfifo_alloc(&c0->c0_fifo,BUF_SIZE,GFP_KERNEL);
	if(ret)
	{
		ret = -ENOMEM;
		kfifo_free(&c0->c0_fifo);
	}

	printk("register %s\n",c0->dev_name);
	return 0;
}
static int __init dev_init(void)
{
	int ret;
	unsigned int i = 0;
	int maj_num;
	dev_t dev_num = MKDEV(g_dev_num,0);
	if(g_dev_num)
		ret = register_chrdev_region(dev_num,DEVICE_NUM,"c0_dev");
	else
		ret=alloc_chrdev_region(&dev_num,0,DEVICE_NUM,"c0_dev");
	if (ret)
	{
		printk("alloc chrdev failed\n");
		return ret;
	}
	maj_num = MAJOR(dev_num);
	c0=(struct c0_dev*)kmalloc(sizeof(struct c0_dev),GFP_KERNEL);
	if(!c0)
	{
		ret = ENOMEM;
		kfree(c0);
		return ret;
	}
	for(i=0;i<DEVICE_NUM;i++)
	{
		chrdev_setup(c0+i,maj_num,i);
	}
	return 0;

}

static void __exit dev_exit(void)
{
	unsigned int i;
	printk("----%s----\n",__func__);
	for(i=0;i<DEVICE_NUM;i++)
	{
		device_destroy((c0+i)->devcls,(c0+i)->dev_num);
		class_destroy((c0+i)->devcls);
		cdev_del((c0+i)->dev);
		unregister_chrdev_region((c0+i)->dev_num,1);
	}
	kfree(c0);
	unregister_chrdev_region(MKDEV(g_dev_num,0),DEVICE_NUM);
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");

